'''
Created on Sep 24, 2009

@author: Marco Aurelio Zoqui
'''

from google.appengine.ext import db
from google.appengine.api import datastore_errors

from django.utils import simplejson

from jail import ZString
from jail import Handler
   

class EditorBuilder():
    cols = []
    validTypes=[]  
    uri = str
    gdBlock = str
    html = str
    jsSourceDir=str
    def __init__(self,uri):
        self.cols = []
        self.uri=uri
        self.gdBlock = '<div id="dyntable" style="width: 100%; table-layout: fixed;"></div>'
        self.template_values={}
        self.validTypes = ['StringProperty','IntegerProperty','ReferenceProperty']
        self.jsSourceDir = "/static/js"
        
    def getTemplateData(self):
        template_values={'grid_script' : self.getJSGridBlock(),
                         'grid_div': self.gdBlock}
        return template_values

    def getHtmlPage(self):
        html = """<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"><head><meta http-equiv="Content-Type" content="text/html;charset=utf-8" /> 
{{grid_script}}</head><body style="margin:0;padding:0;outline:0}">{{grid_div}}</body></html>"""
        return html

    def getJSGridBlock(self):
        scripts={
            'jquery':"http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js",
            'jqueryui':"http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.3/jquery-ui.min.js",
            'autocomplete':"%s/autocomplete.js" % (self.jsSourceDir),
            'jmathparser':"%s/jquery.mathparser.js" % (self.jsSourceDir),
            'jgrideditor':"%s/jquery.jgrideditor.js" % (self.jsSourceDir)
        }
        jsBlock = """<script src="%s" type="text/javascript"></script>
<script src="%s" type="text/javascript"></script>
<script src="%s" type="text/javascript"></script>
<script src="%s" type="text/javascript"></script>
<script src="%s" type="text/javascript"></script>
<script type="text/javascript">
    jQuery(document).ready(function() {
        $('#dyntable').jgrideditor({
            getURL: '%s?param=rows',
            postURL: '%s',
            cssURL: '%s',
            getMsg: 'Loading...',
            postMsg: 'Saving...',
            header: '',
            menuNew: 'New',
            menuSave: 'Save',
            cols: %s,
            rows: []
        });
    });
</script>""" % (scripts['jquery'],
                scripts['jqueryui'],
                scripts['autocomplete'],
                scripts['jmathparser'],
                scripts['jgrideditor'],
                self.uri, 
                self.uri, 
                self.getStyleSheetUrl(),
                simplejson.dumps(self.cols) )
        return jsBlock
    
    def getStyleSheetUrl(self):
        return '/static/css/grid.css'
        
    def setColsFromModel(self,modelClassName):
        indexed = {}
        cols = []       
        for att in modelClassName._properties.values():
            if att.__class__.__name__ in self.validTypes:
                id = modelClassName.__name__+'.'+att.name
                pos="%02d" % att.creation_counter
                label = id
                mathparser=False;
                calcsum = False;
                align = 'left'
                width = '50px'
                choices = None
                sourceUrl=None;
                if att.__dict__.has_key('width'):
                    width=att.width
                if att.__dict__.has_key('align'):
                    align=att.align
                if att.__dict__.has_key('calcsum'):
                    calcsum=(att.calcsum==True)
                if att.verbose_name:
                    label = att.verbose_name
                if att.__class__.__name__=='IntegerProperty':
                    align='right'
                    if att.__dict__.has_key('mathparser'):
                        mathparser=True
                    if not att.__dict__.has_key('width'):
                        width='15px'
                if att.choices and len(att.choices)>0:
                    choices = []
                    for option in att.choices:
                        choices.append(option)
                    choices.sort()
                if att.__class__.__name__=='ReferenceProperty':
                    referenceHelpField = ''
                    if att.__dict__.has_key('referenceHelpField'):
                        referenceHelpField = att.referenceHelpField
                    sourceUrl = self.uri + '?param=reference&model='+att.data_type.__name__+'&referenceHelpField='+referenceHelpField                        
                    id=att.data_type.__name__+'.'+referenceHelpField
                indexed[pos]={ 
                    'id': id, 
                    'label': label,
                    'editable': True,
                    'required': att.required, 
                    'type': '',  
                    'width': width,
                    'align': align,
                    'choices': choices,
                    'sourceURL':sourceUrl,
                    'mathparser': mathparser,
                    'calcsum': calcsum                                                    
                }
                if att.__class__.__name__=='ReferenceProperty' and att.__dict__.has_key('protected'):
                    for cid,colid in enumerate(att.protected.keys()):
                        colpos = "%02d" % cid 
                        column = att.protected[colid]
                        collabel = colid
                        colwidth = width
                        colalign = align
                        colcalcsum = False
                        for xAtt in att.data_type._properties.values():
                            if colid==xAtt.name:
                                collabel=xAtt.verbose_name
                                colpos="%02d" % xAtt.creation_counter
                                break
                        if column.has_key('width'):
                            colwidth=column['width']
                        if column.has_key('align'):
                            colalign=column['align']
                        if column.has_key('calcsum'):
                            colcalcsum=(column['calcsum']==True)
                        sourceUrl = self.uri + '?param=reference&model='+att.data_type.__name__+'&referenceHelpField='+colid
                        colpos=pos+'.'+colpos
                        indexed[colpos]={ 
                            'id': att.data_type.__name__+'.'+colid, 
                            'label': collabel,
                            'editable': False,
                            'required': False, 
                            'type': '',
                            'width': colwidth,
                            'align': colalign,
                            'choices': [],
                            'sourceURL':sourceUrl,
                            'mathparser': False,
                            'calcsum': colcalcsum                                     
                        }                    

        keys = indexed.keys()
        keys.sort()
        for k in keys:
            cols.append(indexed[k])
        if len(cols)>0:
            cols.append({
                'id': 'options', 
                'label': '',
                'editable': False,
                'required': att.required, 
                'type': '',  
                'width': '100px',
                'align': 'right',
                'choices': None,
                'sourceURL':None,
                'mathparser': False,
            })
        self.cols = cols

class DataModelEditor(Handler):
    """
    A DataModelEditor is a RequestHandler implementation with support for model edition.
    
    It handle the request from the browser and manage the associated model object.
    It must be re-implemented by each controlled model.
    The simplest example of DataModelEditor reimplementation:
    
        class City(db.Model):
            code = db.StringProperty(verbose_name='Code')
            name = db.StringProperty(verbose_name='Name')
            
        class CityEditor(DataModelEditor):
            self.model = City
                    
        if __name__ == '__main__':
            application = webapp.WSGIApplication([(r'/',CityEditor)], debug=True)
            wsgiref.handlers.CGIHandler().run(application)
    
    """
   
    def initialize(self, request, response):
        """Re-initialize object.
        
        This method is called after object been initialized. 
        if model was not defined reset all properties."""
        
        super(DataModelEditor,self).initialize(request, response)
        
        myself = dir(self)
        if not 'model' in myself:
            self.model = None
        if not 'resp' in myself:
            self.resp = None
        if not 'rows' in myself:
            self.rows    = None       

    def get(self):
        """
        Handler method for GET requests.

        if param='cols' creates a builder instance to return a
                        html with table definition
        if param='rows' returns a set of items (instance of models) 
                        in json format prepared for edition
        if param='reference' returns the value from a associated entity
        
        """
        if not self.model:
            self.usererror("Model object not defined")
            return
        param = self.getValue("param", "cols")
        if param=='rows':
            #self.response.out.write(simplejson.dumps(self.getRows()))
            self.render('json',self.getRows()) # send rows as json object
            return
        elif param=='cols':
            gridBuilder = EditorBuilder(self.request.uri)
            gridBuilder.setColsFromModel(self.model)
            if self.templatePath and self.templateData:
                self.templateData.update(gridBuilder.getTemplateData())
                self.render(self.templatePath, self.templateData)
            else:
                self.render(gridBuilder.getHtmlPage(), gridBuilder.getTemplateData())
            return
        elif param=='reference':
            model = self.getValue("model")
            field = self.getValue("referenceHelpField")
            typed = ZString(self.getValue("startwithstr")).normalize().upper()
            items = self.getReferenceValues(model,field,typed)
            if not items:
                items = {}
            self.render('json',items)
        else:
            return

    def post(self):
        """
        Handler method for POST requests.

        when options = rowupd perform the onRowUpdate action
             options = rowdel perform the onRowDelete action
             options = None   perform the onValueChanged action
        """
        self.resetError()
        self.resp['rowid']=self.request.get('rowid')
        options = self.request.get('options')
        if options:
            if options =='rowupd' or options=='rowins':
                self.onRowUpdate()
            elif options=='rowdel':
                self.onRowDelete()
        else:  
            self.resp['fieldname']=self.request.get('fieldname')
            self.resp['fieldvalue']=self.request.get('fieldvalue')
            self.onValueChanged()
        #self.response.out.write(simplejson.dumps(self.resp))
        self.render('json',self.resp) # send rows as json object
        return

    def resetError(self):
        """Reset all previous stored error."""
        self.resp = {'sts': 'OK', 
                     'msg': '', 
                     'rowid': None,
                     'fieldname': None,
                     'fieldvalue': None,
                     'color': None} 
    
    def getQuery(self):
        """Return a query object for filtering the items to be displayed."""
        return self.model.all()
    
    def getPropValue(self,item,fieldname):
        """Return the value of a property in database format."""
        for prop in item.properties().values():
            if prop.name==fieldname:
                return prop.get_value_for_datastore(item)
        return None
    
    def addRow(self,id,item):
        """Add a new row data into the grid."""
        row={}
        row['rowid']=id
        for prop in item.properties().values():
            value=''
            if prop.__class__.__name__=='ReferenceProperty':
                referenceHelpField=None
                if prop.__dict__.has_key('referenceHelpField'):
                    referenceHelpField = prop.referenceHelpField
                k = prop.get_value_for_datastore(item)
                if k:
                    objectRef = prop.data_type.get(k)
                    if objectRef:
                        id = objectRef.key().id()
                        row[prop.name+'_newRefId'] = None
                        row[prop.name+'_oldRefId'] = id
                        if referenceHelpField:
                            for prop2 in objectRef.properties().values():
                                colnam=None
                                colvlr=None
                                if prop2.name==referenceHelpField:
                                    colnam=objectRef.__class__.__name__+'.'+prop2.name
                                    colvlr=prop2.get_value_for_datastore(objectRef)
                                elif prop2.name in prop.protected.keys():
                                    colnam=objectRef.__class__.__name__+'.'+prop2.name
                                    colvlr=prop2.get_value_for_datastore(objectRef)
                                if colnam and colvlr:
                                    if prop2.__class__.__name__=='StringProperty':
                                        row[colnam] = ZString(colvlr).normalize().upper()
                                    else:
                                        row[colnam] = colvlr                                     
                        else:
                            row[prop.name] = id
                    else:
                        row[prop.name+'_newRefId'] = None
                        row[prop.name+'_oldRefId'] = None
                        row[prop.name] = None
            else:
                value = prop.get_value_for_datastore(item)
                row[item.__class__.__name__+'.'+prop.name] = value
            
            #logging.debug('rowid: ' + str(id) + ' ' + prop.name + '=' + str(value))            
        self.rows.append(row)

    def getRows(self):
        """Returns the filtered rows."""
        self.rows=[]
        try:
            query = self.getQuery()
            if query:
                for item in query:
                    self.addRow(int(item.key().id()),item)
            self.addRow(-1,self.model())
        except datastore_errors.BadValueError:
            pass
        return self.rows

    def getReferenceQuery(self,model,field,typed):
        """Build the query that read a property from another referenced model."""
        if field:
            return "select * from %s where %s >= '%s' AND %s < '%s' order by %s" %  (model, field, typed, field, (typed+u"\ufffc"), field)
        return None

    def getReferenceValues(self,model,fieldname,typed):
        """It returns the values used by jquery.autocomplete in json format.
        [
            {'id':'31','info':'31','value':'Liberia'},
            {'id':'32','info':'21','value':'Libyan Arab Jamahiriya'},
            {'id':'37','info':'37','value':'Liechtenstein'}
        ]
        """
        qsel = self.getReferenceQuery(model, fieldname, typed)
        items = []
        if qsel:
            query = db.GqlQuery(qsel)
            if query:
                for item in query:
                    value = self.getPropValue(item, fieldname)
                    if value:
                        id = str(item.key().id())
                        items.append({
                            'id':id,
                            'value':value
                        });
        return {'query' : typed, 'results' : items }
                
    def onValueChanged(self):
        """
        Called when a cell is changed.

        you may reimplement it perform any further field validation.
        self.resp['fieldname'] contains the name of the changed field
        self.resp['value'] contains the value of the changed field
        
        if you return a error (setFieldErr) the changed value
        is restored in view layer, and a message is displayed
        
        """
        for prop in self.model._properties.values():
            if prop.__class__.__name__=='ReferenceProperty' and prop.data_type.__name__+'.'+prop.referenceHelpField==self.resp['fieldname']:                   
                item = prop.data_type.get_by_id(int(self.resp['fieldvalue']));
                ret = {}
                for att in item.properties().values():
                    ret[item.__class__.__name__+'.'+att.name]=att.get_value_for_datastore(item)
                #ret[self.resp['fieldname']]=ZString(self.resp['fieldvalue']).normalize().upper()
                #jsonret=simplejson.dumps(ret)
                self.setFieldOK(ret)
                return
                break
            elif (self.model.__name__+'.'+prop.name==self.resp['fieldname']):
                ret={}
                ret[self.resp['fieldname']]=ZString(self.resp['fieldvalue']).normalize().upper()
                #jsonret=simplejson.dumps(ret)
                self.setFieldOK(ret)
                return
                break        

        self.setFieldErr(self.resp['fieldvalue'])
        return
#        if self.resp['fieldvalue']:
#            self.setFieldOK(ZString(self.resp['fieldvalue']).normalize().upper())
#        else:
#            self.setFieldErr(self.resp['fieldvalue'])
    
    def onRowUpdate(self):
        """
        Called when an item is going to be update.


        """
        item = None
        rowid=self.resp['rowid']  
        try:
            if ((not rowid) or (int(rowid)<0)):
                args={}
                for prop in self.model._properties.values():
                    fieldnam=prop.name
                    if prop.__class__.__name__=='ReferenceProperty':
                        refObject = prop.data_type.get_by_id(int(self.request.get(prop.data_type.__name__+'.'+prop.referenceHelpField,None)))
                        if refObject:
                            fieldvlr=refObject
                        else:
                            fieldvlr=None
                    else:
                        fieldvlr=self.request.get(self.model.__name__+'.'+fieldnam,None)
                        if prop.__class__.__name__=='IntegerProperty':
                            fieldvlr=int(fieldvlr)
                    if (fieldnam and fieldvlr):
                        args[fieldnam]=fieldvlr
                item = self.model(**args)
            else:
                item=self.model.get_by_id(int(rowid))
                for argname in self.request.arguments():
                    argvalue=self.request.get(argname,'')
                    if argvalue:
                        for prop in item.properties().values():
                            if item.__class__.__name__+'.'+prop.name==argname:
                                if prop.__class__.__name__=='IntegerProperty':
                                    prop.__set__(item, int(argvalue))
                                elif prop.__class__.__name__=='StringProperty':
                                    prop.__set__(item, argvalue)
                                break
                            elif prop.__class__.__name__=='ReferenceProperty' and prop.data_type.__name__+'.'+prop.referenceHelpField==argname:
                                refObject = prop.data_type.get_by_id(int(argvalue))
                                if refObject:
                                    prop.__set__(item, refObject)
                                    break
        except Exception, e:
            self.resp['sts']='ERROR'
            self.resp['msg']=e.message
            return          

        if item:
            item.put()
            if (int(rowid)<0):
                self.resp['rowid']=item.key().id()
        else:
            self.resp['sts']='ERROR'
        
    def onRowDelete(self):
        """
        Called when an item is going to be deleted.

        """
        item = None
        rowid=self.resp['rowid']    
        if rowid:
            item=self.model.get_by_id(int(rowid))
        if item:
            item.delete()
        else:
            self.resp['sts']='ERROR'

    def setFieldOK(self, data, msg = None):
        """
        Prepare the response to be ok.

        """
        self.resp['sts']='OK'
        self.resp['updated']=data
        if msg:
            self.resp['msg']=msg
        else:
            self.resp['msg']='Saved'
    
    def setFieldErr(self,value, msg = None):
        """
        Prepare the response to be not ok.

        """
        self.resp['sts']='ERROR'
        self.resp['updated']=value
        if msg:
            self.resp['msg']=msg
        else:
            self.resp['msg']='Invalid Value'
            

